/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.nhncorp.usf.core.validation;

import java.util.Date;
import java.util.Locale;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericTypeValidator;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.UrlValidator;
import org.apache.commons.validator.util.ValidatorUtils;

/**
 * validator 함수를 구현한 class
 * <p/>
 *
 */
public class FieldChecks {

	/**
	 * Commons Logging instance.
	 */
	private static final Log LOG = LogFactory.getLog(FieldChecks.class);

	public static final String FIELD_TEST_NULL = "NULL";
	public static final String FIELD_TEST_NOTNULL = "NOTNULL";
	public static final String FIELD_TEST_EQUAL = "EQUAL";

	/**
	 * Checks if the field isn't null and length of the field is greater than
	 * zero not including whitespace.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static boolean validateRequired(Object bean, Field field) {

		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		if ((value != null)) {
			String isTrim = field.getVarValue("trim");
			
			if (isTrim != null && isTrim.equals("true")) {
				value = value.trim();
			}
		}
		
		if ((value == null) || (value.length() == 0)) {
			return false;
		} else {
			return true;
		}
		
	}

	/**
	 * Checks if the field isn't null based on the values of other fields.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @param validator the Validator
	 * @return validation result
	 */
	public static boolean validateRequiredIf(Object bean, Field field, org.apache.commons.validator.Validator validator) {

		Object form = validator.getParameterValue(org.apache.commons.validator.Validator.BEAN_PARAM);
		String value = null;
		boolean required = false;

		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		int i = 0;
		String fieldJoin = "AND";
		
		if (!GenericValidator.isBlankOrNull(field.getVarValue("fieldJoin"))) {
			fieldJoin = field.getVarValue("fieldJoin");
		}

		if (fieldJoin.equalsIgnoreCase("AND")) {
			required = true;
		}

		while (!GenericValidator.isBlankOrNull(field.getVarValue("field[" + i
				+ "]"))) {
			String dependProp = field.getVarValue("field[" + i + "]");
			String dependTest = field.getVarValue("fieldTest[" + i + "]");
			String dependTestValue = field.getVarValue("fieldValue[" + i + "]");
			String dependIndexed = field.getVarValue("fieldIndexed[" + i + "]");

			if (dependIndexed == null) {
				dependIndexed = "false";
			}

			String dependVal = null;
			boolean thisRequired = false;
			
			if (field.isIndexed() && dependIndexed.equalsIgnoreCase("true")) {
				String key = field.getKey();
				
				if ((key.indexOf("[") > -1) && (key.indexOf("]") > -1)) {
					String ind = key.substring(0, key.indexOf(".") + 1);
					dependProp = ind + dependProp;
				}
			}

			dependVal = ValidatorUtils.getValueAsString(form, dependProp);
			
			if (dependTest.equals(FIELD_TEST_NULL)) {
				if ((dependVal != null) && (dependVal.length() > 0)) {
					thisRequired = false;
				} else {
					thisRequired = true;
				}
			}

			if (dependTest.equals(FIELD_TEST_NOTNULL)) {
				if ((dependVal != null) && (dependVal.length() > 0)) {
					thisRequired = true;
				} else {
					thisRequired = false;
				}
			}

			if (dependTest.equals(FIELD_TEST_EQUAL)) {
				thisRequired = dependTestValue.equalsIgnoreCase(dependVal);
			}

			if (fieldJoin.equalsIgnoreCase("AND")) {
				required = required && thisRequired;
			} else {
				required = required || thisRequired;
			}

			i++;
		}

		if (required) {
			if (GenericValidator.isBlankOrNull(value)) {
				return false;
			} else {
				return true;
			}
		}
		
		return true;
	}

	/**
	 * Checks if the field matches the regular expression in the field's mask
	 * attribute.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static boolean validateMask(Object bean, Field field) {

		String mask = field.getVarValue("mask");
		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		try {
			if (GenericValidator.isBlankOrNull(value)
					|| !GenericValidator.matchRegexp(value, mask)) {
				return false;
			} else {
				return true;
			}
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
		}
		return true;
	}

	/**
	 * Checks if the field can safely be converted to a byte primitive.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static Object validateByte(Object bean, Field field) {
		Object result = null;
		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		result = GenericTypeValidator.formatByte(value);

		return result == null ? Boolean.FALSE : result;
	}

	/**
	 * Checks if the field can safely be converted to a short primitive.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static Object validateShort(Object bean, Field field) {
		Object result = null;
		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		result = GenericTypeValidator.formatShort(value);

		return result == null ? Boolean.FALSE : result;
	}

	/**
	 * Checks if the field can safely be converted to an int primitive.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static Object validateInteger(Object bean, Field field) {
		
		Object result = null;
		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		result = GenericTypeValidator.formatInt(value);

		return result == null ? Boolean.FALSE : result;
	}

	/**
	 * Checks if the field can safely be converted to a long primitive.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static Object validateLong(Object bean, Field field) {
		Object result = null;
		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		result = GenericTypeValidator.formatLong(value);

		return result == null ? Boolean.FALSE : result;
	}

	/**
	 * Checks if the field can safely be converted to a float primitive.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static Object validateFloat(Object bean, Field field) {
		Object result = null;
		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		result = GenericTypeValidator.formatFloat(value);

		return result == null ? Boolean.FALSE : result;
	}

	/**
	 * Checks if the field can safely be converted to a double primitive.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static Object validateDouble(Object bean, Field field) {
		Object result = null;
		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}


		result = GenericTypeValidator.formatDouble(value);

		return result == null ? Boolean.FALSE : result;
	}
	
	/**
	 * @param bean the Object
	 * @param field the Field
	 * @param locale the Locale
	 * @return validation result
	 */
	public static Date validateDate(Object bean, Field field, Locale locale) {
		String value = ValidatorUtils.getValueAsString(bean, field.getProperty());

		return GenericTypeValidator.formatDate(value, locale);
	}
	
	/**
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static Date validateDate(Object bean, Field field) {
		String value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		String datePattern = field.getVarValue("datePattern");
		String datePatternStrict = field.getVarValue("datePatternStrict");

		Date result = null;
		
		if (datePattern != null && datePattern.length() > 0) {
			result = GenericTypeValidator.formatDate(value, datePattern, false);
		} else if (datePatternStrict != null && datePatternStrict.length() > 0) {
			result = GenericTypeValidator.formatDate(value, datePatternStrict,
					true);
		}

		return result;
	}

	/**
	 * Checks if a fields value is within a range (min &amp; max specified in
	 * the vars attribute).
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static boolean validateIntRange(Object bean, Field field) {

		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		if (!GenericValidator.isBlankOrNull(value)) {
			try {
				int intValue = Integer.parseInt(value);
				int min = Integer.parseInt(field.getVarValue("min"));
				int max = Integer.parseInt(field.getVarValue("max"));

				if (!GenericValidator.isInRange(intValue, min, max)) {
					return false;
				}
			} catch (Exception e) {
				return false;
			}
		} else {
			return false;
		}
		
		return true;
	}

	/**
	 * Checks if a fields value is within a range (min &amp; max specified in
	 * the vars attribute).
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static boolean validateDoubleRange(Object bean, Field field) {
		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		if (!GenericValidator.isBlankOrNull(value)) {
			try {
				double doubleValue = Double.parseDouble(value);
				double min = Double.parseDouble(field.getVarValue("min"));
				double max = Double.parseDouble(field.getVarValue("max"));

				if (!GenericValidator.isInRange(doubleValue, min, max)) {
					return false;
				}
			} catch (Exception e) {
				return false;
			}
		} else {
			return false;
		}

		return true;
	}

	/**
	 * Checks if a fields value is within a range (min &amp; max specified in
	 * the vars attribute).
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static boolean validateFloatRange(Object bean, Field field) {

		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		if (!GenericValidator.isBlankOrNull(value)) {
			try {
				float floatValue = Float.parseFloat(value);
				float min = Float.parseFloat(field.getVarValue("min"));
				float max = Float.parseFloat(field.getVarValue("max"));

				if (!GenericValidator.isInRange(floatValue, min, max)) {
					return false;
				}
			} catch (Exception e) {
				return false;
			}
		} else {
			return false;
		}

		return true;
	}

	/**
	 * Checks if the field is a valid credit card number.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static Object validateCreditCard(Object bean, Field field) {

		Object result = null;
		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		if (GenericValidator.isBlankOrNull(value)) {
			return false;
		}

		result = GenericTypeValidator.formatCreditCard(value);

		return result == null ? Boolean.FALSE : result;

	}

	/**
	 * Checks if a field has a valid e-mail address.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static boolean validateEmail(Object bean, Field field) {

		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		if (GenericValidator.isBlankOrNull(value)
				|| !GenericValidator.isEmail(value)) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Checks if the field's length is less than or equal to the maximum value.
	 * A <code>Null</code> will be considered an error.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static boolean validateMaxLength(Object bean, Field field) {

		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		if (value != null) {
			try {
				int max = Integer.parseInt(field.getVarValue("maxlength"));

				if (!GenericValidator.maxLength(value, max)) {
					return false;
				}
			} catch (Exception e) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Checks if the field's length is greater than or equal to the minimum
	 * value. A <code>Null</code> will be considered an error.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static boolean validateMinLength(Object bean, Field field) {

		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		if (!GenericValidator.isBlankOrNull(value)) {
			try {
				int min = Integer.parseInt(field.getVarValue("minlength"));

				if (!GenericValidator.minLength(value, min)) {
					return false;
				}
			} catch (Exception e) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Checks if a field has a valid url. Four optional variables can be
	 * specified to configure url validation.
	 * 
	 * @param bean the Object
	 * @param field the Field
	 * @return validation result
	 */
	public static boolean validateUrl(Object bean, Field field) {

		String value = null;
		
		if (isString(bean)) {
			value = (String) bean;
		} else {
			value = ValidatorUtils.getValueAsString(bean, field.getProperty());
		}

		if (GenericValidator.isBlankOrNull(value)) {
			return false;
		}

		// Get the options and schemes Vars
		boolean allowallschemes = "true".equalsIgnoreCase(field.getVarValue("allowallschemes"));
		int options = allowallschemes ? UrlValidator.ALLOW_ALL_SCHEMES : 0;

		if ("true".equalsIgnoreCase(field.getVarValue("allow2slashes"))) {
			options += UrlValidator.ALLOW_2_SLASHES;
		}

		if ("true".equalsIgnoreCase(field.getVarValue("nofragments"))) {
			options += UrlValidator.NO_FRAGMENTS;
		}

		String schemesVar = allowallschemes ? null : field.getVarValue("schemes");

		// No options or schemes - use GenericValidator as default
		if (options == 0 && schemesVar == null) {
			if (GenericValidator.isUrl(value)) {
				return true;
			} else {
				return false;
			}
		}

		// Parse comma delimited list of schemes into a String[]
		String[] schemes = null;
		
		if (schemesVar != null) {

			StringTokenizer st = new StringTokenizer(schemesVar, ",");
			schemes = new String[st.countTokens()];

			int i = 0;
			
			while (st.hasMoreTokens()) {
				schemes[i++] = st.nextToken().trim();
			}

		}

		// Create UrlValidator and validate with options/schemes
		UrlValidator urlValidator = new UrlValidator(schemes, options);
		
		if (urlValidator.isValid(value)) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Return <code>true</code> if the specified object is a String or a
	 * <code>null</code> value.
	 * 
	 * @param object Object to be tested
	 * @return The string value
	 */
	protected static boolean isString(Object object) {
		return (object == null) ? true : String.class.isInstance(object);
	}

}
